#include <iostream>
#include <vector>
#include <string>

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <cassert>
#include <cstdlib>

#define PROCESS_NUM 10
#define MakeSeed() srand((unsigned int)time(nullptr) ^ getpid() ^ rand() % 31415926)

//////////////////////////////////////////////////////////////////////////////子进程要完成的任务
// 函数指针 类型 int (*p)(int,int) p=&add
typedef void (*func_t)();

void downloadTask()
{
    std::cout << getpid() << ": 下载任务\n"
              << std::endl;
    sleep(1);
}

void ioTask()
{
    std::cout << getpid() << ": IO任务\n"
              << std::endl;
    sleep(1);
}

void flushTask()
{
    std::cout << getpid() << ": 刷新任务\n"
              << std::endl;
    sleep(1);
}

// 创建方法集合表
void loadTaskFunc(std::vector<func_t> *funcMap)
{
    assert(funcMap != nullptr);
    funcMap->push_back(downloadTask);
    funcMap->push_back(ioTask);
    funcMap->push_back(flushTask);
}

//////////////////////////////////////////////////////////////////////////////多进程程序
// 用于保存管道和子进程的信息
class SubEp // EndPoint
{
public:
    SubEp(pid_t subId, int writeFd)
        : _subId(subId),
          _writeFd(writeFd)
    {
        char nameBuffer[1024];
        snprintf(nameBuffer, sizeof nameBuffer, "process-%d[pid:%d-fd:%d]", num++, _subId, _writeFd);
        _name = nameBuffer;
    }

public:
    static int num;
    std::string _name;
    pid_t _subId; // 子进程id
    int _writeFd;
};
int SubEp::num = 0;

int recvTask(int readFd)
{
    int code = 0;
    ssize_t s = read(readFd, &code, sizeof code);
    if (s == 4)
        return code;
    else if (s <= 0)
        return -1;
    return -1;
}

void sendTask(const SubEp &process, int taskNum)
{
    std::cout << "send task num:" << taskNum << " send to -> " << process._name << std::endl;
    int n = write(process._writeFd, &taskNum, sizeof taskNum);
    assert(n == sizeof(int));
    (void)n;
}

void creatSubProcess(std::vector<SubEp> *subs, std::vector<func_t> &funcMap)
{
    std::vector<int> deleteFd; // 方法3
    for (int i = 0; i < PROCESS_NUM; i++)
    {
        int fds[2];
        int n = pipe(fds);
        assert(n == 0);

        // 可能的bug在哪？
        // 父进程打开的文件是会被子进程共享的。
        // 比如当我们创建2号进程的时候，由于写实拷贝，2号进程有一个文件描述符指向1管道的写端
        // 这就导致了旧管道会有多个写端（新进程指向这个写端）
        // 解决方法1 最后统一关闭所有子进程，统一等待子进程
        // 2.关闭子进程的时候，从最后一个子进程进行关闭然后等待这个子进程即可
        // 3.创建子进程成功的时候，就把多余的写端关闭
        pid_t id = fork(); // fork子进程返回0，父进程返回子进程id
        if (id == 0)
        {
            // 方法3，每创建一个新进程，删除由于写实拷贝保留的指向之前进程管道的文件描述符
            for (int i = 0; i < deleteFd.size(); i++)
                close(deleteFd[i]);
            
            // 子进程,关闭写
            close(fds[1]);
            while (true)
            {
                // 1.获取命令码, 如果没有发送，就应该阻塞
                int commandCode = recvTask(fds[0]);
                // 2.完成任务
                if (commandCode >= 0 && commandCode < funcMap.size())
                    funcMap[commandCode]();
                else if (commandCode == -1) // 收到-1表示子进程受到非法消息，直接退出
                    break;
            }
            exit(0);
        }

        // 父进程，关闭读
        close(fds[0]);
        SubEp sub(id, fds[1]);
        subs->push_back(sub);
        deleteFd.push_back(fds[1]);
    }
}

void loadBlanceContrl(const std::vector<SubEp> subs, const std::vector<func_t> funcMap, int count)
{
    int subProcessNum = subs.size();
    int taskNum = funcMap.size();
    bool forever = (count == 0 ? true : false);
    while (true)
    {
        // 1.选择一个子进程 --> std::vector<SubEp> subs -> index ->采用随机数
        // 我们将我们的任务均衡的发送给每一个子进程，不至于让某一个子进程压力过大
        // 让子进程进行：负载均衡--单机
        int subIndex = rand() % subProcessNum;

        // 2.选择一个任务 -->  std::vector<func_t> funcMap -> index
        int taskIndex = rand() % taskNum; // 任务码

        // 3.将任务发给进程
        sendTask(subs[subIndex], taskIndex);
        sleep(1);
        if (!forever)
        {
            count--;
            if (count == 0)
                break;
        }
    }
    // write quit -> read 0，由于bug，如果关一个就等待会导致无法正常退出

    // 这种写法只要不关一个就等待，就不会有问题
    for (int i = 0; i < subProcessNum; i++)
        close(subs[i]._writeFd);
}

void waitProcess(const std::vector<SubEp> &subs)
{
    int processnum = subs.size();
    for (int i = 0; i < processnum; i++)
    {
        int status = 0;
        waitpid(subs[i]._subId, 0, 0);
        std::cout << "wait process success ...:" << subs[i]._subId;
        std::cout << " exitCode:" << ((status >> 8) & 0xff) << " exitSignal:" << (status & 0x7f) << std::endl;
    }
}

int main()
{
    MakeSeed();
    // 1.建立和子进程通信的信道,有bug，但不影响编写
    //可以使用[子进程id, 写入进程fd],这里选择或者创建一个类，使用vector保存

    // 1.1 加载方法表
    std::vector<func_t> funcMap; // 函数指针表
    loadTaskFunc(&funcMap);

    // 1.2 创建子进程，并且维护好父子进程通信信道
    std::vector<SubEp> subs; // 子进程封装
    creatSubProcess(&subs, funcMap);

    // 2.走到这里是父进程！控制子进程，负载均衡控制子进程
    int taskCnt = 10; // 让子进程完成的任务数量，用0表示永远控制
    loadBlanceContrl(subs, funcMap, taskCnt);

    // 3.回收子进程
    waitProcess(subs);
    return 0;
}